home *** CD-ROM | disk | FTP | other *** search
- /**
- * Copyright (c) 2008, Jose Enrique Bolanos, Jorge Villalobos
- * All rights reserved.
- *
- * THE CONTENTS OF THIS FILE ARE *NOT* OPEN SOURCE, AS IT CONTAINS CONFIDENTIAL
- * INFORMATION THAT SHOULD NOT BE USED BY OTHER APPLICATIONS, EVEN IF THEY
- * INTEND TO CONNECT TO THE LAST.FM API.
- *
- * Read http://www.last.fm/api/webauth for more information on secret/public
- * keys.
- **/
-
- var EXPORTED_SYMBOLS = [];
-
- const Cc = Components.classes;
- const Ci = Components.interfaces;
-
- Components.utils.import("resource://firefm/fmCommon.js");
-
- // Regular expression used for unescaping control characters.
- const RE_UNESCAPE_REPLACE = /!\d\d?\d?!/g;
- // Pre-computed multiplicative inverse in GF(2^8).
- const SBOX =
- [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
- 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
- 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
- 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
- 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
- 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
- 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
- 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
- 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
- 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
- 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
- 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
- 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
- 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
- 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
- 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
- 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
- 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
- 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
- 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
- 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
- 0xb0, 0x54, 0xbb, 0x16 ];
- // Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)].
- const RCON =
- [ [ 0x00, 0x00, 0x00, 0x00 ], [ 0x01, 0x00, 0x00, 0x00 ],
- [ 0x02, 0x00, 0x00, 0x00 ], [ 0x04, 0x00, 0x00, 0x00 ],
- [ 0x08, 0x00, 0x00, 0x00 ], [ 0x10, 0x00, 0x00, 0x00 ],
- [ 0x20, 0x00, 0x00, 0x00 ], [ 0x40, 0x00, 0x00, 0x00 ],
- [ 0x80, 0x00, 0x00, 0x00 ], [ 0x1b, 0x00, 0x00, 0x00 ],
- [ 0x36, 0x00, 0x00, 0x00 ] ];
- // Hexadecimal character mapping.
- const HEX_CHARS = "0123456789abcdef";
- // MD5 bits per input character. 8 - ASCII; 16 - Unicode.
- const MD5_BITS_PER_CHAR = 8;
-
- const ENC = "ck6krit14yAhMTYwIUsFSoYv7aHtRPD7TCuVwUKcPLiiFbNuSwRTyiEwIV4=";
-
- var appKey = null;
-
- /**
- * Handles Fire.fm's secret information, specifically it's application secret
- * key, used to sign information sent to the Last.fm API.
- */
- FireFM.Secret = {
- /* Logger for this object. */
- _logger : null,
-
- /**
- * Initializes this object.
- */
- init : function() {
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- let that = this;
-
- this._logger = FireFM.getLogger("FireFM.Secret");
- this._logger.debug("init");
- },
-
- /* The not-so-secret API key. */
- get API_KEY() { return "96565ee5297d63435d890a7f8320e890"; },
-
- /**
- * Generates an authentication string used to create a Scrobble session.
- * @param aTimestamp timestamp used to generate the string.
- * @return the authentication string for the Scrobble handshake.
- */
- generateScrobbleAuth : function(aTimestamp) {
- this._logger.debug("generateScrobbleAuth");
-
- if (null == appKey) {
- appKey = this._decryptAES128(ENC, FireFM.EXTENSION_UUID);
- }
-
- return this._md5Hash(appKey + aTimestamp);
- },
-
- /**
- * Generates a signature for the parameter string.
- * @param aParams the parameter string to be sent to the API.
- * @return the signature that serves to authenticate this application and the
- * parameters being sent to the API.
- */
- generateSignature : function(aParams) {
- this._logger.debug("generateSignature");
-
- if (null == appKey) {
- appKey = this._decryptAES128(ENC, FireFM.EXTENSION_UUID);
- }
-
- return this._md5Hash(aParams + appKey);
- },
-
- /**
- * Decrypts a string of data using the 128 bit AES encryption algorithm.
- * Note: This method is based on code inside Fire Encrypter:
- * https://addons.mozilla.org/en-US/firefox/addon/3208
- * @author Ronald van den Heetkamp.
- * @author Jorge Villalobos (minor modifications)
- * @param aEncryptedData the data to decrypt.
- * @param aKey the key used to decrypt the data.
- * @return the decrypted data with the 128 bit AES algorithm, using the given
- * key.
- * @throws Exception if any of the arguments is invalid.
- */
- _decryptAES128 : function(aEncryptedData, aKey) {
- this._logger.trace("_decryptAES128");
-
- let hiddenWindow =
- Cc["@mozilla.org/appshell/appShellService;1"].
- getService(Ci.nsIAppShellService).hiddenDOMWindow;
- let pwBytes = new Array(16);
- let counterBlock = new Array(16);
- let pwKeySchedule =
- this._keyExpansion([ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 ]);
- let key;
- let keySchedule;
- let ctrTxt;
- let plaintext;
- let cipherCntr;
- let pt;
- let encryptedDataByte;
- let plaintextByte;
-
- for (let i = 0; i < 16; i++) {
- pwBytes[i] = aKey.charCodeAt(i);
- }
-
- key = this._cipher(pwBytes, pwBytes, pwKeySchedule);
- keySchedule = this._keyExpansion(key);
- // split aEncryptedData into array of block-length strings.
- aEncryptedData = hiddenWindow.atob(aEncryptedData).split('+');
- // recover nonce from 1st element of aEncryptedData.
- ctrTxt = this._unescCtrlChars(aEncryptedData[0]);
-
- for (let i = 0; i < 8; i++) {
- counterBlock[i] = ctrTxt.charCodeAt(i % 4);
- }
-
- plaintext = new Array(aEncryptedData.length - 1);
-
- for (let b = 1; b < aEncryptedData.length; b++) {
- for (let c = 0; c < 8; c++) {
- // set counter in counter block.
- counterBlock[15 - c] = ((b - 1) >>> c * 8) & 0xff;
- }
- // encrypt counter block.
- cipherCntr = this._cipher(counterBlock, key, keySchedule);
- aEncryptedData[b] = this._unescCtrlChars(aEncryptedData[b]);
- pt = '';
-
- for (let i = 0; i < aEncryptedData[b].length; i++) {
- encryptedDataByte = aEncryptedData[b].charCodeAt(i);
- plaintextByte = encryptedDataByte ^ cipherCntr[i];
- pt += String.fromCharCode(plaintextByte);
- }
-
- plaintext[b] = pt;
- }
-
- return unescape(plaintext.join(''));
- },
-
- /**
- * Apply the AES cypher.
- * @param aInput the input to cypher.
- * @param aKey the key used to encrypt / decrypt.
- * @param aKeySchedule the key shedule to use for the cypher.
- * @return input string with the cypher applied to it.
- */
- _cipher : function(aInput, aKey, aKeySchedule) {
- this._logger.trace("_cipher");
-
- let Nk = aKey.length / 4; // key length (in words).
- let Nr = Nk + 6; // no of rounds.
- let Nb = 4; // block size: no of columns in state (fixed at 4 for AES).
- let state = [[],[],[],[]];
- let output;
-
- // initialise 4xNb byte-array 'state' with input.
- for (let i = 0; i < 4 * Nb; i++) {
- state[i % 4][Math.floor(i / 4)] = aInput[i];
- }
-
- state = this._addRoundKey(state, aKeySchedule, 0, Nb);
-
- for (let round = 1; round < Nr; round++) {
- state = this._subBytes(state, Nb);
- state = this._shiftRows(state, Nb);
- state = this._mixColumns(state, Nb);
- state = this._addRoundKey(state, aKeySchedule, round, Nb);
- }
-
- state = this._subBytes(state, Nb);
- state = this._shiftRows(state, Nb);
- state = this._addRoundKey(state, aKeySchedule, Nr, Nb);
- output = new Array(4 * Nb); // convert to 1-d array before returning
-
- for (let i = 0; i< 4 * Nb; i++) {
- output[i] = state[i % 4][Math.floor(i / 4)];
- }
-
- return output;
- },
-
- /**
- * Apply sbox to state S [§5.1.1].
- * @param aState the state to transform.
- * @param aColumnCount the number of columns in the state.
- * @return the transformed state.
- */
- _subBytes : function(aState, aColumnCount) {
- this._logger.trace("_subBytes");
-
- for (let r = 0; r < 4; r++) {
- for (let c = 0; c < aColumnCount; c++) {
- aState[r][c] = SBOX[aState[r][c]];
- }
- }
-
- return aState;
- },
-
- /**
- * Shift row r of state S left by r bytes [§5.1.2].
- * @param aState the state to transform.
- * @param aColumnCount the number of columns in the state.
- * @return the transformed state.
- */
- _shiftRows : function(aState, aColumnCount) {
- this._logger.trace("_shiftRows");
-
- let t = new Array(4);
- // note that this will work for Nb=4,5,6, but not 7,8: see
- // fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
- for (let r = 1; r < 4; r++) {
- for (let c = 0; c < 4; c++) {
- t[c] = aState[r][(c + r) % aColumnCount]; // shift into temp copy.
- }
-
- for (let c = 0; c < 4; c++) {
- aState[r][c] = t[c]; // and copy back.
- }
- }
-
- return aState;
- },
-
- /**
- * Combine bytes of each col of state S [§5.1.3].
- * @param aState the state to transform.
- * @param aColumnCount the number of columns in the state.
- * @return the transformed state.
- */
- _mixColumns : function(aState, aColumnCount) {
- this._logger.trace("_mixColumns");
-
- let a;
- let b;
-
- for (let c = 0; c < 4; c++) {
- a = new Array(4); // 'a' is a copy of the current column from 's'.
- b = new Array(4); // 'b' is a•{02} in GF(2^8).
-
- for (let i = 0; i < 4; i++) {
- a[i] = aState[i][c];
- b[i] =
- ((aState[i][c] & 0x80) ? (aState[i][c] << 1 ^ 0x011b) :
- (aState[i][c] << 1));
- }
-
- // a[n] ^ b[n] is a•{03} in GF(2^8).
- aState[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
- aState[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
- aState[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
- aState[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
- }
-
- return aState;
- },
-
- /**
- * xor Round Key into state S [§5.1.4].
- * @param aState the state to transform.
- * @param aKeySchedule the key shedule to use for the cypher.
- * @param aRound the round number.
- * @param aColumnCount the number of columns in the state.
- * @return the transformed state.
- */
- _addRoundKey : function(aState, aKeySchedule, aRound, aColumnCount) {
- this._logger.trace("_addRoundKey");
-
- for (let r = 0; r < 4; r++) {
- for (let c = 0; c < aColumnCount; c++) {
- aState[r][c] ^= aKeySchedule[aRound * 4 + c][r];
- }
- }
-
- return aState;
- },
-
- /**
- * Generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2].
- * @param aKey the key used to generate the schedule.
- * @return the key schedule generated with the key.
- */
- _keyExpansion : function(aKey) {
- this._logger.trace("_keyExpansion");
-
- let Nk = aKey.length / 4; // key length (in words).
- let Nr = Nk + 6; // no of rounds.
- let Nb = 4; // block size: no of columns in state (fixed at 4 for AES).
- let w = new Array(Nb * (Nr + 1));
- let temp = new Array(4);
- let r;
-
- for (let i = 0; i < Nk; i++) {
- r = [aKey[4 * i], aKey[4 * i + 1], aKey[4 * i + 2], aKey[4 * i + 3]];
- w[i] = r;
- }
-
- for (let i = Nk; i < (Nb * (Nr + 1)); i++) {
- w[i] = new Array(4);
-
- for (let t = 0; t < 4; t++) {
- temp[t] = w[i - 1][t];
- }
-
- if (i % Nk == 0) {
- temp = this._subWord(this._rotWord(temp));
-
- for (let t = 0; t < 4; t++) {
- temp[t] ^= RCON[i / Nk][t];
- }
- } else if (Nk > 6 && i % Nk == 4) {
- temp = this._subWord(temp);
- }
-
- for (let t = 0; t < 4; t++) {
- w[i][t] = w[i-Nk][t] ^ temp[t];
- }
- }
-
- return w;
- },
-
- /**
- * Apply sbox to 4-byte word w.
- * @param aWord the word to transform.
- * @return the transformed word.
- */
- _subWord : function(aWord) {
- this._logger.trace("_subWord");
-
- for (let i = 0; i < 4; i++) {
- aWord[i] = SBOX[aWord[i]];
- }
-
- return aWord;
- },
-
- /**
- * Rotate 4-byte word w left by one byte.
- * @param aWord the word to transform.
- * @return the transformed word.
- */
- _rotWord : function(aWord) {
- this._logger.trace("_rotWord");
-
- aWord[4] = aWord[0];
-
- for (let i = 0; i < 4; i++) {
- aWord[i] = aWord[i + 1];
- }
-
- return aWord;
- },
-
- /**
- * Unescape potentially problematic control characters.
- * @param aString the string to unescape.
- * @return the unescaped string.
- */
- _unescCtrlChars : function(aString) {
- this._logger.trace("_unescCtrlChars");
-
- let unescaped =
- aString.replace(
- RE_UNESCAPE_REPLACE,
- function(c) { return String.fromCharCode(c.slice(1, -1)); });
-
- return unescaped;
- },
-
- /**
- * Generates the MD5 hash of the given string. Taken from
- * http://developer.mozilla.org/en/docs/nsICryptoHash#
- * Computing_the_Hash_of_a_String
- * @param aString the string to hash to MD5.
- * @return the hashed string.
- */
- _md5Hash : function(aString) {
- this._logger.trace("_md5Hash");
-
- let converter =
- Cc["@mozilla.org/intl/scriptableunicodeconverter"].
- createInstance(Ci.nsIScriptableUnicodeConverter);
- let hash =
- Components.classes["@mozilla.org/security/hash;1"].
- createInstance(Components.interfaces.nsICryptoHash);
- let decoder =
- function(aCharCode) { return ("0" + aCharCode.toString(16)).slice(-2); };
- let data;
- let hashedData;
-
- converter.charset = "UTF-8";
- // data is an array of bytes.
- data = converter.convertToByteArray(aString, {});
-
- // perform the hashing operation.
- hash.init(hash.MD5);
- hash.update(data, data.length);
- hashedData = hash.finish(false);
-
- // convert the binary hash data to a hex string.
- return [decoder(hashedData.charCodeAt(i)) for (i in hashedData)].join("");
- }
- };
-
- /**
- * FireFM.Secret constructor.
- */
- (function() {
- this.init();
- }).apply(FireFM.Secret);
-